/**
 * @file    range.h
 * @author  Ying-Po Liao <lrobin@neo.tamu.edu>
 * @version 1.0
 *
 * @section Header file for array iterator
 *
 * CSCE-689 Design Using C++
 * Final Project
 */

#ifndef ARRAY_ITERATOR_H
#define ARRAY_ITERATOR_H

#include <iterator>

template<typename T, unsigned int DIM>
class array_iterator
{
public:
	typedef typename std::remove_cv<T>::type	no_const_T;
	typedef std::forward_iterator_tag		iterator_category;
	typedef no_const_T				value_type;
	typedef std::ptrdiff_t				difference_type;
	typedef T*					pointer;
	typedef T&					reference;
	
	array_iterator() : data_(0) { }

	array_iterator( const array<no_const_T, DIM>& arr );

	bool operator==(const array_iterator<T, DIM>& x) const 
	{  return data_ == x.data_;  }

	bool operator!=(const array_iterator<T, DIM>& x) const 
	{  return data_ != x.data_;  }

	T& operator*()  const {  return *data_;  }
	T* operator->() const {  return data_;  }

	array_iterator<T, DIM>& operator++();

	array_iterator<T, DIM> operator++(int)
	{
		array_iterator<T, DIM> tmp = *this;
		++(*this); 
		return tmp;
	}

	const tiny_vector<int, DIM>& position() const { return pos_; }

private:
	tiny_vector<int, DIM> strides_, lbound_, extent_;
	tiny_vector<unsigned int, DIM> order_;
	no_const_T* stack_[DIM];
	no_const_T* last_[DIM];
	int stride_;
	int max_dim_;
	tiny_vector<int, DIM> pos_;
	no_const_T* data_;
};

template <typename T, unsigned int DIM>
array_iterator<T, DIM>::array_iterator( const array<no_const_T, DIM>& arr )
{
	// Making internal copies of these avoids keeping
	// a pointer to the array and doing indirection.
	strides_ = arr.stride();
	lbound_ = arr.lbound();
	extent_ = arr.shape();
	order_ = arr.storage().get_storage_order();
	data_ = const_cast<no_const_T*>( arr.data_first() );

	max_dim_ = order_(0);
	stride_ = strides_( max_dim_ );

	for( unsigned int dim = 0; dim < DIM; ++dim)
	{
	    stack_[dim] = data_;
	    last_[dim] = data_ + extent_( order_(dim) ) * strides_( order_(dim) );
	}
	pos_ = lbound_;
}

template <typename T, unsigned int DIM>
array_iterator<T, DIM>& array_iterator<T, DIM>::operator++()
{
	data_ += stride_;

	if (data_ != last_[0])
	{
		// We hit this case almost all the time.
		++pos_[ max_dim_ ];
		return *this;
	}

	// We've hit the end of a row/column/whatever.  Need to
	// increment one of the loops over another dimension.

	int dim = 1;
	for ( ;dim < DIM; ++dim )
	{
		unsigned int r = order_(dim);
		data_ = stack_[dim] + strides_[r];
		++pos_(r);
		if ( data_ != last_[dim] )
		    break;
	}

	// All done?
	if( dim == DIM )
	{
		// Setting data_ to 0 indicates the end of the array has
		// been reached, and will match the end iterator.
		data_ = 0;
		return *this;
	}

	stack_[dim] = data_;
	
	// Now reset all the last pointers
	for( --dim; dim >= 0; --dim )
	{
		unsigned int r2 = order_(dim);
		stack_[dim] = data_;
		last_[dim] = data_ + extent_(r2) * strides_(r2);
		pos_(r2) = lbound_(r2);
	}
	return *this;
}

#endif //!< ARRAY_ITERATOR_H
